﻿/*  ----------------------------------------------------------------------------
 *  EcoSqlCondition (http://ecosqlcondition.codeplex.com)
 *  ----------------------------------------------------------------------------
 *  License:    http://ecosqlcondition.codeplex.com/license  
 *  ----------------------------------------------------------------------------
 *  Company:    P3-Studio (http://www.p3studio.pl)
 *  File:       PersistenceMapperSqlConditionImpl.cs
 *  Author:     Paweł Pawłow
 *  ----------------------------------------------------------------------------
 */

using System;
using Eco.Services;
using Eco.DataRepresentation;
using Eco.Persistence.Connection;
using Eco.Persistence.ORMapping;
using Eco.UmlRt;

namespace Eco.Persistence
{
	internal class PersistenceMapperSqlConditionImpl : ChainedPersistenceMapperImplBase
	{
		private const string EcoTypeColumnName = "eco_type";
		private const string EcoIdColumnName = "eco_id";

		public PersistenceMapperSqlConditionImpl(
			ITypeSystemService typeSystemService,
			IPersistenceMapper nextPersistenceMapper,
			PersistenceMapperDb pmapperDb)
			: base(typeSystemService, nextPersistenceMapper)
		{
			_pmapperDb = pmapperDb;
		}

		public override void FetchIDListWithCondition(
			AbstractCondition condition,
			out ObjectIdList result,
			int maxResults,
			int offset)
		{
			if (condition is SqlCondition)
				HandleSqlCondition((SqlCondition)condition, out result, maxResults, offset);
			else
				NextPersistenceMapper.FetchIDListWithCondition(condition, out result, maxResults, offset);
		}

		readonly PersistenceMapperDb _pmapperDb;

		void HandleSqlCondition(
			SqlCondition condition,
			out ObjectIdList result,
			int maxResults,
			int offset)
		{
			result = new ObjectIdList();
			IDatabase db = _pmapperDb.ConnectionPool.RetrieveDatabaseConnection(false);
			try
			{
				IQuery q = db.GetQuery();
				try
				{

					DiscriminatorValueCollection dvc = GetDiscriminatorValueCollection().Values;
					KeyDefinition keydef = GetKeyDefinition(condition.ClassId);

					q.AssignSqlText(condition.Sql);

					if (condition.Env != null)
						foreach (var p in condition.Env)
						{
							var param = q.ParamByName(p.Name);
							if (p.ExternalVarvalue is ObjectId)
								param.Value = ((ObjectId)p.ExternalVarvalue).Key;
							else if (p.ClassifierName == @"System.String")
							{
								var t = Type.GetType(p.ExternalVarvalue.ToString(), false, true);
								param.Value = t != null ? TypeToEcoTypeValue(t) : p.ExternalVarvalue;
							}
							else
								param.Value = p.ExternalVarvalue;
						}

					q.Open();
					try
					{
						if (offset > 0)
							q.MoveBy(offset);

						var fields = new[] { q.FieldByName(EcoIdColumnName) };
						var ecoTypeColumn = q.FieldByName(EcoTypeColumnName);
						var keyDefAttribMappings = keydef.Attributemappings;
						var keyMapper = keydef.KeyMapper;
						var condVersion = condition.Version;

						for (var i = maxResults; !q.Eof && (i != 0); i--)
						{
							var dbType = ecoTypeColumn.Value.ToString();
							if (!string.IsNullOrEmpty(dbType)) // Ignore NULL rows.															
								result.Add(keyMapper.IdFromFields(
									fields, keyDefAttribMappings, dvc.FindByValue(dbType).Class_.ClassId, condVersion));
							q.Next();
						}
					}
					finally
					{
						q.Close();
					}
				}
				finally
				{
					db.ReleaseQuery(q);
				}
			}
			finally
			{
				_pmapperDb.ConnectionPool.ReturnDatabaseConnection(db);
			}
		}

		private string TypeToEcoTypeValue(Type type)
		{
			var c = (IClass)TypeSystemService.TypeSystem.GetClassifierByType(type);
			return GetDiscriminatorValueCollection().DiscriminatorValuesByClassId(c.InternalIndex).Value;
		}

		private KeyDefinition GetKeyDefinition(int classId)
		{
			IClass c = TypeSystemService.TypeSystem.AllClasses[classId];
			ClassDefinition classDef = _pmapperDb.EffectiveRunTimeMappingProvider.Mapping.Classes[c.Name];
			return _pmapperDb.EffectiveRunTimeMappingProvider.Mapping.FindPrimaryKeyDefinition(classDef);
		}

		private DiscriminatorDef GetDiscriminatorValueCollection()
		{
			IClass rootClass = TypeSystemService.TypeSystem.AllClasses[0];
			ClassDefinition rootClassDef = _pmapperDb.EffectiveRunTimeMappingProvider.Mapping.Classes[rootClass.Name];
			DiscriminatorDef def = null;
			foreach (DiscriminatorDef def2 in rootClassDef.Discriminators)
				def = def2;
			if (def == null)
				throw new NullReferenceException(@"Root class has no discriminator defined!");
			return def;
		}
	}
}
